/**
*	@author : Ha Le Hoai Trung
*	MSSV : 50402810
*	@version 1.0
*	7/12/2007
* 	This file provides a simple version of code generator
*
*/
package MP.codegeneration;

import java.util.ArrayList;

import MP.*;
import MP.ASTs.*;
import MP.lexicalanalysis.Token;
import MP.staticchecker.*;

public class CodeGenerator {
	AST astTree;
	public CodeGenerator(AST a){
		astTree = a;
	}
	public void parse() throws CompilationException{
		Visitor v = new AddTypeConversion();
		astTree.visit(v,null);
		//v = new AstPrinter();
		//astTree.visit(v,null);
		v = new CodeGenVisitor();
		astTree.visit(v, null);
	}
}

class CodeGenVisitor implements Visitor {
	private SymTable sym;
	private Emitter em;

	public CodeGenVisitor() {
		sym = new SymLinearTable();
		em = new Emitter();
	}
	// ProgramAST
	public Object visitProgramAST(ProgramAST ast, Object o)
			throws CompilationException {
		em.printout(em.emitPROLOG());
		sym.enterScope();
		Frame fr = new Frame();
		em.setFrame(fr);
		fr.enterScope();

		em.printout(em.emitCLINIT());

		Type mType = new FunctionType(new ArrayType(new StringType()),new VoidType());
		em.printout(em.emitMETHOD("main",mType,true));	
		em.printout((String)ast.c.visit(this, o));
		em.printout(em.emitRETURN(null));
		//fake get new Index for void main(arg)
		//Reason: to generate emitENDMETHOD correctly at .local
		em.getFrame().getNewIndex();
		em.printout(em.emitENDMETHOD());
		fr.exitScope();
		sym.exitScope();
		return null;
	}

	// VarDeclPartAST
	public Object visitVarDeclPartAST(VarDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// EmptyVarDeclPartAST
	public Object visitEmptyVarDeclPartAST(EmptyVarDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// VarDeclAST
	public Object visitVarDeclAST(OneDeclAST ast, Object o)
			throws CompilationException {
		return null;					
	}

	// ConstDeclAST
	public Object visitConstDeclAST(OneDeclAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// ArrayTypeAST
	public Object visitArrayTypeAST(ArrayTypeAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// EmptyExprListAST
	public Object visitEmptyExprListAST(EmptyExprListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// ExprListAST
	public Object visitExprListAST(ExprListAST ast, Object o)
			throws CompilationException {
		String eCode = (String)ast.e.visit(this, o);
		return eCode;

	}

	// ProDeclPartAST
	public Object visitProDeclPartAST(ProDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// EmptyProDeclPartAST
	public Object visitEmptyProDeclPartAST(EmptyProDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// FuncDeclAST
	public Object visitFuncDeclAST(FuncDeclAST fAst, Object o)
			throws CompilationException {
		return null;
	}

	// ProcDeclAST
	public Object visitProcDeclAST(ProcDeclAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// ParaListAST
	public Object visitParaListAST(ParaListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// EmptyParaListAST
	public Object visitEmptyParaListAST(EmptyParaListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// BoolTypeAST
	public Object visitBoolTypeAST(BoolTypeAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// IntTypeAST
	public Object visitIntTypeAST(IntTypeAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// RealTypeAST
	public Object visitRealTypeAST(RealTypeAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// StmtListAST
	public Object visitStmtListAST(StmtListAST ast, Object o)
			throws CompilationException {
		String oCode = (String) ast.o.visit(this, o);
		String sCode = (String) ast.s.visit(this, o);
		return oCode+sCode;
	}

	// EmptyStmtListAST
	public Object visitEmptyStmtListAST(EmptyStmtListAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// AssiStmtAST
	public Object visitAssiStmtAST(AssiStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// IfThenElseStmtAST
	public Object visitIfThenElseStmtAST(IfThenElseStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// IfThenStmtAST
	public Object visitIfThenStmtAST(IfThenStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// RepeatStmtAST
	public Object visitRepeatStmtAST(RepeatStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// RetNullStmtAST
	public Object visitRetNullStmtAST(RetNullStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// RetStmtAST
	public Object visitRetStmtAST(RetStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// CallStmtAST
	public Object visitCallStmtAST(CallStmtAST ast, Object o)
			throws CompilationException {
		SymEntry entry = (SymEntry)sym.lookup(ast.name.Lexeme);
		String argCode = (String)ast.e.visit(this, o);
		String funcCode = argCode + em.emitINVOKESTATIC(((String)entry.getObject())+"/"+ast.name.Lexeme,entry.getType());
		return funcCode;
	}

	// ForStmtAST
	public Object visitForStmtAST(ForStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// WhileStmtAST
	public Object visitWhileStmtAST(WhileStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// BreakStmtAST
	public Object visitBreakStmtAST(BreakStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// CompStatAST
	public Object visitCompStatAST(CompStatAST ast, Object o)
			throws CompilationException {
		
		sym.enterScope();
		em.getFrame().enterScope();
		ast.v.visit(this, o);
		String sLabel = em.emitLABEL(em.getFrame().getStartLabel());
		String sCode = (String) ast.s.visit(this, o);		
		String eLabel = em.emitLABEL(em.getFrame().getEndLabel());
		em.getFrame().exitScope();
		sym.exitScope();
		return sLabel+sCode+eLabel;
	}

	// ContStmtAST
	public Object visitContStmtAST(ContStmtAST ast, Object o)
			throws CompilationException {
		return "";
	}

	public Object visitLvalueAST(LvalueAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// IntLiteralAST
	public Object visitIntLiteralAST(IntLiteralAST ast, Object o)
			throws CompilationException {
		return em.emitPUSHCONST(ast.literal.Lexeme,Type.INTTYPE);
	}

	public Object visitRealLiteralAST(RealLiteralAST ast, Object o)
			throws CompilationException {
		return "";
	}

	public Object visitBoolLiteralAST(BoolLiteralAST ast, Object o)
			throws CompilationException {
		return "";
	}

	public Object visitStringLiteralAST(StringLiteralAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// UnaryExprAST
	public Object visitUnaryExprAST(UnaryExprAST ast, Object o)
			throws CompilationException {

		return "";
	}

	// BinExprAST
	public Object visitBinExprAST(BinExprAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// VarExprAST
	public Object visitVarExprAST(VarExprAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// EleExprAST
	public Object visitEleExprAST(EleExprAST ast, Object o)
			throws CompilationException {
		return "";
	}

	// CallExprAST
	public Object visitCallExprAST(CallExprAST ast, Object o)
			throws CompilationException {
		return "";
	}

	public Object visitCastInt2RealAST(CastInt2RealAST ast, Object o) 
			throws CompilationException {
		String t = (String) ast.e.visit(this, o);
		return t + em.emitI2F();
	}
}

class AddTypeConversion implements Visitor {
	SymLinearTable sym;
	AddTypeConversion() {
		sym = new SymLinearTable();
	}
	// ProgramAST
	public Object visitProgramAST(ProgramAST ast, Object o)
			throws CompilationException {
		ast.v.visit(this, o);
		ast.p.visit(this, o);
		ast.c.visit(this, o);
		sym.exitScope();
		return null;
	}

	public Object visitVarDeclPartAST(VarDeclPartAST ast, Object o)
			throws CompilationException {
		ast.o.visit(this, o);
		ast.v.visit(this, o);
		return null;
	}

	// EmptyVarDeclPartAST
	public Object visitEmptyVarDeclPartAST(EmptyVarDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// VarDeclAST
	// return type
	public Object visitVarDeclAST(OneDeclAST ast, Object o)
			throws CompilationException {
		VarDeclAST vAst = (VarDeclAST) ast;
		Type t = (Type) vAst.t.visit(this, o);
		sym.insertId(new SymEntry(vAst.name,t));
		return t;
	}

	// ConstDeclAST
	public Object visitConstDeclAST(OneDeclAST ast, Object o)
			throws CompilationException {
		ConstDeclAST cAst = (ConstDeclAST) ast;
		String[] str = new String[1];
		Type t = (Type) cAst.l.visit(this, str);
		sym.insertId(new SymEntry(cAst.name,t,str[0]));
		return null;
	}

	// ArrayTypeAST
	public Object visitArrayTypeAST(ArrayTypeAST ast, Object o)
			throws CompilationException {
		RangeType rt = new RangeType(Integer.parseInt(ast.lower.Lexeme)
					    ,Integer.parseInt(ast.upper.Lexeme));
		ArrayType at = new ArrayType(rt,(Type)ast.type.visit(this,o));
		return at;
	}

	// EmptyExprListAST
	public Object visitEmptyExprListAST(EmptyExprListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// ExprListAST
	public Object visitExprListAST(ExprListAST ast, Object o)
			throws CompilationException {
		Type et = (Type) ast.e.visit(this, null);
		if (o != null) {
			ProductType pt = (ProductType) o;
			Type t1 = pt.getE1Type();
			Type t2 = pt.getE2Type();
			if (t1 == Type.REALTYPE && et == Type.INTTYPE) {
				ExprAST na = new CastInt2RealAST(ast.e);
				ast.e = na;
				na.parent = ast;
			}
			ast.l.visit(this, t2);
		} else
			ast.l.visit(this,o);
		return null;
	}

	// ProDeclPartAST
	public Object visitProDeclPartAST(ProDeclPartAST ast, Object o)
			throws CompilationException {
		ast.o.visit(this, o);
		ast.p.visit(this, o);
		return null;
	}

	// EmptyProDeclPartAST
	public Object visitEmptyProDeclPartAST(EmptyProDeclPartAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// FuncDeclAST
	public Object visitFuncDeclAST(FuncDeclAST fAst, Object o)
			throws CompilationException {
		SymEntry entry = new SymEntry(fAst.name,null);
		sym.insertId(entry);
		sym.enterScope();
		Type pType = (Type) fAst.para.visit(this, o);
		Type rType = (Type) fAst.ret.visit(this, o);
		FunctionType ft = new FunctionType(pType,rType);
		entry.setType(ft);
		fAst.v.visit(this, o);
		fAst.p.visit(this, o);
		fAst.c.visit(this, o);
		sym.exitScope();
		return null;
	}

	// ProcDeclAST
	public Object visitProcDeclAST(ProcDeclAST pAst, Object o)
			throws CompilationException {
		SymEntry entry = new SymEntry(pAst.name,null);
		sym.insertId(entry);
		sym.enterScope();
		Type pType = (Type) pAst.para.visit(this, o);
		FunctionType ft = new FunctionType(pType,Type.VOIDTYPE);
		entry.setType(ft);
		pAst.v.visit(this, o);
		pAst.p.visit(this, o);
		pAst.c.visit(this, o);
		sym.exitScope();
		return null;
	}

	// ParaListAST
	// return Type
	public Object visitParaListAST(ParaListAST pAst, Object o)
			throws CompilationException {
		Type t1 = (Type) pAst.v.visit(this, o);
		Type t2 = (Type) pAst.p.visit(this, o);
		
		return new ProductType(t1,t2);
	}

	// EmptyParaListAST
	// return Type
	public Object visitEmptyParaListAST(EmptyParaListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// BoolTypeAST
	// return Type	
	public Object visitBoolTypeAST(BoolTypeAST ast, Object o)
			throws CompilationException {
		return Type.BOOLTYPE;
	}

	// IntTypeAST
	// return Type	
	public Object visitIntTypeAST(IntTypeAST ast, Object o)
			throws CompilationException {
		return Type.INTTYPE;
	}

	// RealTypeAST
	// return Type	
	public Object visitRealTypeAST(RealTypeAST ast, Object o)
			throws CompilationException {
		return Type.REALTYPE;
	}

	// StmtListAST
	public Object visitStmtListAST(StmtListAST sAst, Object o)
			throws CompilationException {
		sAst.o.visit(this, o);
		sAst.s.visit(this, o);
		return null;
	}

	// EmptyStmtListAST
	public Object visitEmptyStmtListAST(EmptyStmtListAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// AssiStmtAST
	public Object visitAssiStmtAST(AssiStmtAST aAst, Object o)
			throws CompilationException {
		Type l = (Type) aAst.l.visit(this, o);
		Type r = (Type) aAst.e.visit(this, o);
		if (l instanceof FunctionType) 		
			l = ((FunctionType) l).getReturnType();
		if (l == Type.REALTYPE && r == Type.INTTYPE) {
			ExprAST ea = new CastInt2RealAST(aAst.e);
			aAst.e =  ea;
			ea.parent = aAst;
		}			
		return null;
	}

	// IfThenElseStmtAST
	public Object visitIfThenElseStmtAST(IfThenElseStmtAST iAst, Object o)
			throws CompilationException {
		iAst.e.visit(this, o);
		iAst.s1.visit(this, o);
		iAst.s2.visit(this, o);
		return null;
	}

	// IfThenStmtAST
	public Object visitIfThenStmtAST(IfThenStmtAST iAst, Object o)
			throws CompilationException {
		iAst.e.visit(this, o);
		iAst.s1.visit(this, o);
		return null;
	}

	// RepeatStmtAST
	public Object visitRepeatStmtAST(RepeatStmtAST rAst, Object o)
			throws CompilationException {
		rAst.s.visit(this, o);
		rAst.e.visit(this, o);
		return null;
	}

	// RetNullStmtAST
	public Object visitRetNullStmtAST(RetNullStmtAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// RetStmtAST
	public Object visitRetStmtAST(RetStmtAST rAst, Object o)
			throws CompilationException {
		return null;
	}

	// CallStmtAST
	public Object visitCallStmtAST(CallStmtAST ast, Object o)
			throws CompilationException {

		Type pt = null;
		SymEntry entry = sym.lookup(ast.name.Lexeme);
		if (entry != null && entry.getType() instanceof FunctionType) {
			FunctionType ft = (FunctionType)entry.getType();
			pt = ft.getParameterType();
		}
		ast.e.visit(this,pt);
		return null;
	}

	// ForStmtAST
	public Object visitForStmtAST(ForStmtAST fAst, Object o)
			throws CompilationException {
		fAst.e1.visit(this,o);
		fAst.e2.visit(this,o);
		fAst.o.visit(this,o);
		return null;
	}

	// WhileStmtAST
	public Object visitWhileStmtAST(WhileStmtAST wAst, Object o)
			throws CompilationException {
		wAst.e.visit(this,o);
		wAst.o.visit(this,o);
		return null;
	}

	// BreakStmtAST
	public Object visitBreakStmtAST(BreakStmtAST ast, Object o)
			throws CompilationException {
		return null;
	}

	// CompStatAST
	public Object visitCompStatAST(CompStatAST cAst, Object o)
			throws CompilationException {
		sym.enterScope();
		cAst.v.visit(this,o);
		cAst.s.visit(this,o);
		sym.exitScope();
		return null;
	}

	// ContStmtAST
	public Object visitContStmtAST(ContStmtAST ast, Object o)
			throws CompilationException {
		return null;
	}
	// return Type
	public Object visitLvalueAST(LvalueAST ast, Object o)
			throws CompilationException {
		Type t = null;
		SymEntry entry = sym.lookup(ast.name.Lexeme);
		if (entry != null) {
			if (ast.e instanceof EmptyExprListAST)
				t = entry.getType();
			else if (entry.getType() instanceof ArrayType) {
				ArrayType at = (ArrayType) entry.getType();
				t = at.getEType();
			}
		}
		ast.e.visit(this, o);
		return t;
	}

	// LiteralAST
	// return Type
	public Object visitIntLiteralAST(IntLiteralAST ast, Object o)
			throws CompilationException {
		String[] t = (String []) o;
		if (t != null) 
			t[0] = ast.literal.Lexeme;
		return Type.INTTYPE;

	}
	// return Type
	public Object visitRealLiteralAST(RealLiteralAST ast, Object o)
			throws CompilationException {
		String[] t = (String []) o;
		if (t != null)
			t[0] = ast.literal.Lexeme;
		return Type.REALTYPE;				
	}
	// return Type
	public Object visitBoolLiteralAST(BoolLiteralAST ast, Object o)
			throws CompilationException {
		String[] t = (String []) o;
		if (t != null)
			t[0] = ast.literal.Lexeme;
		return Type.BOOLTYPE;			
	}
	// return Type
	public Object visitStringLiteralAST(StringLiteralAST ast, Object o)
			throws CompilationException {
		String[] t = (String []) o;
		if (t != null)
			t[0] = ast.literal.Lexeme;
		return Type.STRINGTYPE;			
	}

	// UnaryExprAST
	// return Type
	public Object visitUnaryExprAST(UnaryExprAST ast, Object o) 
			throws CompilationException {
		Type t = (Type) ast.e.visit(this,o);
		return t;
	}

	// BinExprAST
	public Object visitBinExprAST(BinExprAST ast, Object o)
			throws CompilationException {
		Type e1 = (Type) ast.e1.visit(this, o);
		Type e2 = (Type) ast.e2.visit(this, o);
		Type rType = e1;
		if (e1 == Type.REALTYPE && e2 == Type.INTTYPE) {
			ExprAST t = new CastInt2RealAST(ast.e2);
			ast.e2 = t;
			t.parent = ast;
			rType = Type.REALTYPE;
		} else if (e2 == Type.REALTYPE && e1 == Type.INTTYPE) {
			ExprAST t = new CastInt2RealAST(ast.e1);
			ast.e1 = t;
			t.parent = ast;
			rType = Type.REALTYPE;
		} 
		if (ast.op.Lexeme.equals("/"))
			rType = Type.REALTYPE;
		else if (ast.op.Kind == Token.RELOP || ast.op.Kind == Token.EQOP)
			rType = Type.BOOLTYPE;
		return rType;
	}

	// VarExprAST
	// return Type
	public Object visitVarExprAST(VarExprAST ast, Object o)
			throws CompilationException {
		SymEntry entry = sym.lookup(ast.name.Lexeme);
		if (entry != null) return entry.getType();
		return null;
	}

	// EleExprAST
	// return Type
	public Object visitEleExprAST(EleExprAST eAst, Object o)
			throws CompilationException {
		SymEntry entry = sym.lookup(eAst.name.Lexeme);
		eAst.e.visit(this, o);
		Type t = null;
		if (entry != null)
			t = entry.getType();
		if (t != null && t instanceof ArrayType) {
			ArrayType at = (ArrayType) t;
			return at.getEType();
		}
		return null;
	}

	// CallExprAST
	public Object visitCallExprAST(CallExprAST cAst, Object o) throws CompilationException{
		Type rt = null;
		Type pt = null;
		SymEntry entry = sym.lookup(cAst.name.Lexeme);
		if (entry != null && entry.getType() instanceof FunctionType) {
			FunctionType ft = (FunctionType)entry.getType();
			rt = ft.getReturnType();
			pt = ft.getParameterType();
		}
		cAst.e.visit(this,pt);
		return rt;		
	}
	
	public Object visitCastInt2RealAST(CastInt2RealAST ast, Object o) 
		throws CompilationException {
		//ast.e.visit(this,o);
		return Type.REALTYPE;
	}
}